package weaver.model;

import java.util.*;
import java.io.*;
import java.nio.file.*;

/**
 * The WeaverModel class encapsulates the core logic of the Weaver game.
 * It manages the game state, dictionary loading, word validation, and player progression.
 * The class extends Observable to notify views or controllers when the game state changes.
 */
public class WeaverModel extends Observable {
    private Set<String> dictionary; // Set of valid 4-letter words
    private String startWord;       // The word from which the player starts
    private String targetWord;      // The goal word
    private List<String> currentPath; // List of moves made by the player
    private boolean isValidating;     // Flag to enable dictionary validation
    private boolean isShowingPath;    // Flag to show solution path
    private boolean isRandomWords;    // Flag to use random start/target words
    private GameSolver gameSolver;    // Solver to compute shortest path
    private List<String> solutionPath; // Computed solution path

    /**
     * Constructs a WeaverModel and initializes the game dictionary and solver.
     */
    public WeaverModel() {
        this.dictionary = new HashSet<>();
        this.currentPath = new ArrayList<>();
        this.solutionPath = new ArrayList<>();
        loadDictionary();
        assert !dictionary.isEmpty() : "Dictionary should not be empty after loading";
        this.gameSolver = new GameSolver(dictionary);
    }

    /**
     * Loads the dictionary from known file paths.
     * Throws runtime exceptions if loading fails.
     */
    private void loadDictionary() {
        try {
            String[] possiblePaths = {
                    "src/weaver/model/dictionary.txt",      // 主要路径
                    "weaver/model/dictionary.txt",          // 备用路径
                    "aoop_cw_weavergame/src/weaver/model/dictionary.txt",  // 完整项目路径
                    "dictionary.txt",                       // 根目录
                    "../dictionary.txt",                    // 上级目录
                    "./dictionary.txt",                     // 当前目录
                    "out/production/aoop_coursework/weaver/model/dictionary.txt"  // 编译输出目录
            };

            Path dictionaryPath = null;

            for (String path : possiblePaths) {
                Path tryPath = Paths.get(path);
                if (Files.exists(tryPath)) {
                    dictionaryPath = tryPath;
                    break;
                }
            }

            if (dictionaryPath == null) {
                throw new FileNotFoundException("Dictionary file not found in any of the expected locations");
            }

            dictionary = new HashSet<>(Files.readAllLines(dictionaryPath));
            if (dictionary.isEmpty()) {
                throw new IOException("Dictionary is empty");
            }

            System.out.println("Successfully loaded " + dictionary.size() + " words from dictionary");

        } catch (IOException e) {
            System.err.println("Error loading dictionary: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * Sets flags that control game behavior.
     * Automatically initializes words if needed.
     */
    public void setFlags(boolean isValidating, boolean isShowingPath, boolean isRandomWords) {
        assert startWord != null || !isValidating : "Start word must be set before enabling validation";
        if (startWord == null) {
            setWords("COLD", "WARM"); // Default fallback
        }

        this.isValidating = isValidating;
        this.isShowingPath = isShowingPath;
        this.isRandomWords = isRandomWords;
        setChanged();
        notifyObservers();
    }

    /**
     * Initializes the game with given start and target words.
     * Validates words and computes the solution path.
     */
    public void setWords(String startWord, String targetWord) {
        assert startWord != null && targetWord != null : "Words cannot be null";
        assert startWord.length() == 4 && targetWord.length() == 4 : "Words must be 4 letters long";
        assert dictionary != null && !dictionary.isEmpty() : "Dictionary must be initialized and non-empty";

        startWord = startWord.toLowerCase();
        targetWord = targetWord.toLowerCase();

        if (isValidating) {
            assert dictionary.contains(startWord) && dictionary.contains(targetWord)
                    : "Words must be in dictionary";
        }

        this.startWord = startWord;
        this.targetWord = targetWord;
        this.currentPath.clear();
        this.currentPath.add(this.startWord.toUpperCase());

        // If both words are the same, choose a new random target
        if (this.startWord.equals(this.targetWord)) {
            do {
                this.targetWord = getRandomWord();
            } while (this.targetWord.equals(this.startWord));
        }

        // Initialize solver and get optimal solution path
        gameSolver.initializeGame(this.startWord, this.targetWord);
        this.solutionPath = gameSolver.findShortestPath();

        // Reset path with only the start word
        this.currentPath.clear();
        this.currentPath.add(this.startWord.toUpperCase());

        setChanged();
        notifyObservers();
    }

    /**
     * Returns whether the given word is valid according to the dictionary.
     */
    public boolean isValidWord(String word) {
        word = word.toLowerCase();
        if (!isValidating) return true;
        return dictionary.contains(word);
    }

    /**
     * Returns whether the proposed move is valid (i.e., one-letter difference).
     */
    public boolean isValidMove(String word) {
        assert word != null && word.length() == 4 : "Move word must be non-null and 4 letters long";
        if (!isValidWord(word)) {
            return false;
        }

        String lastWord = currentPath.get(currentPath.size() - 1).toLowerCase();
        assert lastWord.length() == 4 : "Last word in path must be 4 letters long";

        int differences = 0;
        for (int i = 0; i < 4; i++) {
            if (lastWord.charAt(i) != word.charAt(i)) {
                differences++;
            }
        }

        return differences == 1;
    }

    /**
     * Adds a valid move to the path.
     * Notifies observers on change.
     */
    public void makeMove(String word) {
        assert word != null : "Word to move cannot be null";
        assert isValidMove(word) : "Invalid move";
        assert !isGameWon() : "Game already won. No more moves allowed.";

        word = word.toUpperCase();
        currentPath.add(word);

        setChanged();
        notifyObservers();
    }

    /**
     * Returns whether the game has been won (i.e., last word == target).
     */
    public boolean isGameWon() {
        if (currentPath.isEmpty()) return false;
        String last = currentPath.get(currentPath.size() - 1).toLowerCase();
        return last.equals(targetWord.toLowerCase());
    }

    // === Getter methods ===

    public String getStartWord() {
        return startWord;
    }

    public String getTargetWord() {
        return targetWord;
    }

    public List<String> getCurrentPath() {
        return new ArrayList<>(currentPath);
    }

    public boolean isValidating() {
        return isValidating;
    }

    /**
     * Resets the current path to just the starting word.
     */
    public void resetGame() {
        currentPath.clear();
        if (startWord != null) {
            currentPath.add(startWord.toUpperCase());
        }
        setChanged();
        notifyObservers();
    }

    /**
     * Selects a random word from the dictionary.
     */
    public String getRandomWord() {
        assert dictionary != null && !dictionary.isEmpty() : "Dictionary must be initialized before selecting a random word";
        List<String> words = new ArrayList<>(dictionary);
        return words.get(new Random().nextInt(words.size()));
    }

    public boolean isShowingPath() {
        return isShowingPath;
    }

    public boolean isRandomWords() {
        return isRandomWords;
    }

    /**
     * Returns the shortest solution path from start to target.
     */
    public List<String> getSolutionPath() {
        return solutionPath;
    }
}
